/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

package sagroup.pdlime;

import java.util.Hashtable;
import java.util.List;


import lights.adapters.Tuple;
import lime2.BootException;
import lime2.Location;
import lime2.NamingException;
import lime2.PolicyException;
import lime2.ReactionEvent;
import lime2.ReactionListener;
import lime2.Translator;

/**
 * The primary class used by application programmers. Lime programming basically
 * is getting a reference to LimeTupleSpace objects and manipulating them. <br>
 * Please note that Lime protects itself against "wrong" arguments passed to it;
 * IllegalArgumentExceptions are thrown. Parameter checking is enforced by:
 * <P>
 * &#9679 constructors of the (public) classes that are instantiated by users
 * (LimeTemplate and Reaction) <br>
 * &#9679 methods of LimeTupleSpace
 * </P>
 * In order to understand why it is sufficient to patrol these points, let us
 * consider the <u>flows of data</u>. <br>
 * <br>
 * <b><i>Users to Lime</i></b> <br>
 * Application programmers can pass data to the Lime system in the following
 * ways:
 * <P>
 * &#9679 instantiating LimeTemplates <br>
 * &#160 &#160 .. for the LimeTupleSpace <i>in</i>, <i>inp</i>, <i>ing</i>,
 * <i>rd</i>, <i>rdp</i>, <i>rdg</i> operations <br>
 * &#160 &#160 .. for creating Reactions <br>
 * &#9679 instantiating Reactions <br>
 * &#160 &#160 .. for the LimeTupleSpace <i>addStrongReaction</i> operation
 * <br>
 * &#160 &#160 .. for the LimeTupleSpace <i>addWeakReaction</i> operation <br>
 * &#9679 performing the LimeTupleSpace operations <i>out</i>/<i>outg</i> of
 * ITuple(s)
 * </P>
 * <b><i>Lime to Users</i></b> <br>
 * <br>
 * The following ways are used by the Lime system to return data to the
 * application programmers:
 * <P>
 * &#9679 LimeTuples returned as the results of the LimeTupleSpace operations
 * <br>
 * &#9679 LimeTuples returned as part of ReactionEvents, within reaction
 * listeners <br>
 * &#9679 ReactionIDs returned because of the installation of Reactions
 * </P>
 * <b>Notes for Proper Usage</b> <br>
 * &#9679 not all Strings are <i>legal</i> tuplespace names: a valid name must
 * contain only Unicode letters or digits (see comment <i>Naming</i> in class
 * TupleSpaceProxy) <br>
 * &#9679 before a reference to a LimeTupleSpace can be acquired, we impose the
 * LimeServer to be already booted (if not, an exception is thrown). This isn't
 * really necessary if programmers need only the "local" features of
 * LimeTupleSpace (i.e., no communication with other hosts). But this guarantees
 * that inner Lime components can rely on a booted LimeServer.
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */

public class PiccoTVRLimeTupleSpace extends TVRLimeTupleSpace {

	private PiccoTVRLimeTupleSpace(String name, byte pol)
			throws PolicyException {
		super(name, pol);
	}

	synchronized public static PiccoTVRLimeTupleSpace get(String name)
			throws PolicyException {
		return get(name, lime2.Policies.DEFAULT);
	}

	/**
	 * Same as the previous "get" method; but this one allows to set policy of
	 * the LimeTupleSpace at creation time. Please note that if the
	 * LimeTupleSpace already exists with a different policy, a PolicyException
	 * is thrown. Lime doesn't force consensus on a policy for the tuplespaces
	 * of a federation: it is the application programmer (.. 's agent/s) that
	 * must select the same policy on every host (.. and this makes sense).
	 * 
	 * @param name
	 *            of the LimeTupleSpace we want to use
	 * @param pol
	 *            policy we want to apply to the LimeTupleSpace
	 * @return LimeTupleSpace
	 * @throws BootException
	 * @throws NamingException
	 * @throws PolicyException
	 */
	synchronized public static PiccoTVRLimeTupleSpace get(String name, byte pol)
			throws PolicyException {

		PiccoTVRLimeTupleSpace ret = null;
		if (name != null) {
			ret = (PiccoTVRLimeTupleSpace) TVRLimeTuplespaces.get(name);
			if (ret == null) {
				ret = new PiccoTVRLimeTupleSpace(name, pol);
				TVRLimeTuplespaces.put(name, ret);
			} else if (pol != ret.lts.getPolicy())
				throw new PolicyException("policy has already been set to "
						+ Translator.translatePolicy(ret.lts.getPolicy()));
		}
		return ret;
	}

	/**
	 * @Requires: nothing.
	 * @Modifies: nothing.
	 * @Effects: Private method, we assure parameter is valid. If either mode is
	 *           invalid, throw exception. <br>
	 *           Else do as paper. Note the return value can be null.
	 * @param p
	 * @param updatePolicy
	 * @param consistenceMode
	 */
	private RegisteredReplicaRequest addreplicaRequest(final TVRLimeTemplate p,
			final int updatePolicy) {
		RegisteredReplicaRequest toReturn = null;

		toReturn = new RegisteredReplicaRequest(
				addWeakReaction(new VersionedReaction[] { new VersionedReaction(
						VersionedReaction.ONCEPERTUPLE, p,
						new KeepOrUpdateIfFromRemoteReactionListener()) }));
		return toReturn;
	}

	/**
	 * @Requires: nothing.
	 * @Modifies:
	 * @Effects: If not null, remove the corresponding reactions which concerned
	 *           replica.
	 * @param toBeRemoved
	 * @return
	 */
	private boolean[] removeReplicaRequest(RegisteredReplicaRequest toBeRemoved) {
		if (toBeRemoved == null) {
			return new boolean[0];
		}

		// TODO: maybe should set ids=null for the sake of dangling reference.
		return removeWeakReaction(toBeRemoved.ids);
	}

	private void removeReplicaRequest(List toBeRemoved) {
		RegisteredReplicaRequest[] arr = (RegisteredReplicaRequest[]) toBeRemoved
				.toArray(new RegisteredReplicaRequest[50]);
		for (int i = 0; i < arr.length; i++) {
			removeReplicaRequest(arr[i]);
		}
	}

	/**
	 * @Requires: nothing.
	 * @Modified: nothing.
	 * @Effects: if HERE lack and from remote, then keep it.<br>
	 *           Else if HERE's replica is older and from remote, then update.
	 * @author ibm
	 * 
	 */
	class KeepOrUpdateIfFromRemoteReactionListener implements
			ReactionListener {

		public void reactsToOut(ReactionEvent re) {
			TVRLimeTuple fromRemote = new TVRLimeTuple(re.getLimeTuple());
			if (!fromRemote.isLocal()) {
				// must rd from HERE, and must use rdp not inp, because if
				// fromRemote is not newer than local version, than no reaction
				// should be activated.
				TVRLimeTuple vlt = rdp(new TVRLimeTemplate(null, Location.HERE,
						Location.HERE, null, fromRemote.getVID(), null,
						Location.UNSPECIFIED, Location.UNSPECIFIED, null,
						new Tuple()));
				if (vlt != null) {
					if (vlt.isOlderThan(fromRemote)) {
						updateLocalReplica(fromRemote);
					}
				} else {
					keepRemoteToLocal(fromRemote);
				}
			}
		}

		@Override
		public void reactsToTake(ReactionEvent re) {
			// TODO Auto-generated method stub
			
		}
	};

	/**
	 * @Requires: nothing.
	 * @Modifies: nothing.
	 * @Effects: If <code>fromRemote</code> is surely from remote<br>
	 *           out the tuple.<br>
	 *           Else assert error. Note this method is private and only be
	 *           called by one place and that place ensure the fromRomote is
	 *           surely from remote. So we just use assert not exception.
	 * 
	 */
	private TVRLimeTuple keepRemoteToLocal(TVRLimeTuple fromRemote) {
		// parameter checking
		assert !fromRemote.isLocal() : "must from remote!";

		fromRemote.isReplica = Boolean.TRUE;
		// <code>lts.out()</code> will make the replica's cur and dest to be
		// HERE.
		return out(fromRemote);
	}

	/**
	 * @Requires: nothing.
	 * @Modifies: nothing.
	 * @Effects: If <code>fromRemote</code> is surely from remote<br>
	 *           out the tuple.<br>
	 *           Else assert error. Note this method is private and only be
	 *           called by one place and that place ensure the fromRomote is
	 *           surely from remote.
	 * 
	 */
	private TVRLimeTuple updateLocalReplica(TVRLimeTuple fromRemote) {
		// parameter checking
		assert !fromRemote.isLocal() : "must from remote!";
		TVRLimeTuple vlt = inp(new TVRLimeTemplate(null, Location.HERE,
				Location.HERE, null, fromRemote.getVID(), null,
				Location.UNSPECIFIED, Location.UNSPECIFIED, null, new Tuple()));

		return keepRemoteToLocal(fromRemote);
	}

	public void changeUpdatePolicy(TupleType tupleType, byte newUP) {
		assert (newUP == PDPolicy.MASTER) || (newUP == PDPolicy.ANY);
		PDPolicy old = policyMgr.get(tupleType);
		byte oldUP = old.getUpdatePolicy();
		if (newUP == oldUP)
			return;

		byte rp = old.getReplicationPolicy();

		switch (rp) {
		case PDPolicy.FR:
			// note because if code run here, then newUP must be diff. wtih
			// oldUP,
			// so we can just switch according oldUP or newUP.
			switch (oldUP) {
			case PDPolicy.ANY:// must from ANY to MASTR
				removeReplicaRequest((List) replicaRequests.get(tupleType));
				inp(new TVRLimeTemplate(null, Location.HERE, Location.HERE,
						tupleType, null, null, Location.UNSPECIFIED,
						Location.UNSPECIFIED, Boolean.TRUE, new Tuple()));
				addreplicaRequest(new TVRLimeTemplate(null,
						Location.UNSPECIFIED, Location.UNSPECIFIED, tupleType,
						null, null, Location.UNSPECIFIED, Location.UNSPECIFIED,
						Boolean.FALSE, new Tuple()), newUP);
				break;
			case PDPolicy.MASTER:// must from MASTR to ANY
				removeReplicaRequest((List) replicaRequests.get(tupleType));
				// no need delete replicas, just deal with reactions.
				addreplicaRequest(new TVRLimeTemplate(null,
						Location.UNSPECIFIED, Location.UNSPECIFIED, tupleType,
						null, null, Location.UNSPECIFIED, Location.UNSPECIFIED,
						null, new Tuple()), newUP);
				break;
			default:
				break;
			}
			break;
		case PDPolicy.RC:
			// TODO: delete replicas?
			switch (oldUP) {
			case PDPolicy.ANY:// must from MASTR to ANY
				break;
			case PDPolicy.MASTER:// must from ANY to MASTR
				break;
			default:
				break;
			}
			break;
		case PDPolicy.SO:
			// when SO, ANY and MASTER is no use.
			switch (oldUP) {
			case PDPolicy.ANY:// must from MASTR to ANY
				break;
			case PDPolicy.MASTER:// must from ANY to MASTR
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}

		// at last we modify hashtable.
		policyMgr.policies.put(tupleType, new PDPolicy(old
				.getReplicationPolicy(), newUP));

	}

	public void changeReplicationPolicy(TupleType tupleType, byte newRP) {
		assert (newRP == PDPolicy.SO) || (newRP == PDPolicy.RC)
				|| (newRP == PDPolicy.FR);
		PDPolicy old = policyMgr.get(tupleType);
		byte up = old.getUpdatePolicy();
		byte oldRP = old.getReplicationPolicy();
		if (newRP == oldRP)
			return;

		switch (oldRP) {
		case PDPolicy.FR:
			// note because updatePolicy only influence isReplica field of
			// TVRLimeTemplate.
			// we can just switch according oldRP and newRP, no need UP.
			switch (newRP) {
			case PDPolicy.RC:
				removeReplicaRequest((List) replicaRequests.get(tupleType));
				inp(new TVRLimeTemplate(null, Location.HERE, Location.HERE,
						tupleType, null, null, Location.UNSPECIFIED,
						Location.UNSPECIFIED, Boolean.TRUE, new Tuple()));
				// no need addReplicaRequest now.
				break;
			// no case of FR, newRP must be diff. with oldRP.
			default:
				break;
			}
			break;
		case PDPolicy.RC:
			switch (newRP) {
			case PDPolicy.FR:
				removeReplicaRequest((List) replicaRequests.get(tupleType));
				// no need delete replicas.
				addreplicaRequest(new TVRLimeTemplate(null,
						Location.UNSPECIFIED, Location.UNSPECIFIED, tupleType,
						null, null, Location.UNSPECIFIED, Location.UNSPECIFIED,
						(up == PDPolicy.MASTER ? Boolean.FALSE : null),
						new Tuple()), newRP);
				break;
			case PDPolicy.SO:
				removeReplicaRequest((List) replicaRequests.get(tupleType));
				inp(new TVRLimeTemplate(null, Location.HERE, Location.HERE,
						tupleType, null, null, Location.UNSPECIFIED,
						Location.UNSPECIFIED, Boolean.TRUE, new Tuple()));
				break;
			default:
				break;
			}
			break;
		case PDPolicy.SO:
			switch (newRP) {
			case PDPolicy.FR:
				addreplicaRequest(new TVRLimeTemplate(null,
						Location.UNSPECIFIED, Location.UNSPECIFIED, tupleType,
						null, null, Location.UNSPECIFIED, Location.UNSPECIFIED,
						(up == PDPolicy.MASTER ? Boolean.FALSE : null),
						new Tuple()), newRP);
				break;
			case PDPolicy.MASTER:// must from ANY to MASTR
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}

		// at last we modify hashtable.
		policyMgr.policies.put(tupleType, new PDPolicy(newRP, up));
	}

	PolicyMgr policyMgr = new PolicyMgr();
	// <TupleType, List<RegisteredReplicaRequests>>
	Hashtable replicaRequests = new Hashtable();
}

class PolicyMgr {
	// <TupleType, PDPolicy>
	Hashtable policies = new Hashtable();

	PDPolicy get(TupleType tupleType) {
		PDPolicy ret = (PDPolicy) policies.get(tupleType);
		if (null == ret) {
			return new PDPolicy(PDPolicy.SO, PDPolicy.MASTER);
		} else
			return ret;
	}
}